#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <iostream>
#include "TraceManager.h"
#include "TraceMacros.h"
#include <sys/time.h>

namespace tsc
{
namespace common
{

using namespace std;

const char* traceGroupName[] =
{
  "INFO",        //  0
  "TRACE1",      //  1
  "TRACE2",      //  2
  "TRACE3",      //  3
  "TRACE4",      //  4
  "TRACE5",      //  5
  "TRACE6",      //  6
  "TRACE7",      //  7
  "TRACE8",      //  8
  "TRACE9",      //  9
  "ERROR",       // 10
  "ENTER",       // 11
  "RETURN",      // 12
  "STATE_CHANGE",// 13
  "TRACE_PARAM", // 14
  "TRACE_ERROR", // 15
  "BUS_SEND",    // 16
  "BUS_RECEIVE", // 17
  "SIG_SEND",    // 18
  "SIG_RECEIVE", // 19
// last element
  0
};


/*******************************************************************************
 *
 * Singleton instance
 *
 ******************************************************************************/
TraceManager* TraceManager::_instance = 0;

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
TraceManager* TraceManager::instance()
{
	if(!_instance)
	{
		_instance = new TraceManager();
	}
	return _instance;
} // TraceManager::instance

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
TraceManager::TraceManager()
  : _processName("process_name"),
    _traceFile(""),
    _traceDirection(DIRECT_TO_STDERR),
    _OFS(0)
{
  pthread_mutex_init(&_mutex, 0);
  for (int i = 0; i< END_GROUP; ++i)
  {
    _traceState[i] = false;
  }
  // trace objects enabled by default
  _traceState[INFO_GROUP]         = true;
  _traceState[STATE_CHANGE_GROUP] = true;
  _traceState[TRACE_ERROR_GROUP]  = true;
  _traceState[TRACE1_GROUP]       = true;
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
TraceManager::~TraceManager()
{
  TraceObjMap::iterator it = _traceObjMap.begin();
  while(it != _traceObjMap.end())
  {
	  delete it->second;
	  _traceObjMap.erase(it);
	  ++it;
  }
  pthread_mutex_destroy(&_mutex);
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
void
TraceManager::setProcessName(const char* aName)
{
  _processName = aName;
}

void
TraceManager::declareTraceObj(const char* aTraceObjName)
{
	if(aTraceObjName == NULL)
	{
		TRACE_ERROR("Trying to declare TraceObject with NULL name");
		return;
	}

	INFO("Declaring new TraceObj: '%s'", aTraceObjName);
	pthread_mutex_lock(&_mutex);
	TraceObj* pTraceObj = 0;
	if(!findTraceObj(aTraceObjName, pTraceObj))
	{
		string traceObjName(aTraceObjName);
		_traceObjMap.insert(
				std::pair<std::string, TraceObj*>(traceObjName, new TraceObj(aTraceObjName)));
	}
	pthread_mutex_unlock(&_mutex);
}

void
TraceManager::setTraceObjState(
		const char* aTraceObjName,
		traceGroup_e tg,
		bool value)
{
	if(aTraceObjName == NULL)
	{
		TRACE_ERROR("Trying to enable TraceObject with NULL name");
		return;
	}
	pthread_mutex_lock(&_mutex);
	TraceObj* traceObj = 0;
	if(!findTraceObj(aTraceObjName, traceObj))
	{
		pthread_mutex_unlock(&_mutex);
		return;
	}

	if(value)
	{
		traceObj->enable(tg);
	}
	else
	{
		traceObj->disable(tg);
	}

	pthread_mutex_unlock(&_mutex);
}

void
TraceManager::enableTraceObj(const char* aTraceObjName, traceGroup_e tg)
{
	setTraceObjState(aTraceObjName, tg, true);
}

void
TraceManager::disableTraceObj(const char* aTraceObjName, traceGroup_e tg)
{
	setTraceObjState(aTraceObjName, tg, false);
}

void
TraceManager::enableAllTraceObj(const char* aTraceObjName)
{
	for (size_t tg = 0; tg < END_GROUP; ++tg)
	{
		setTraceObjState(aTraceObjName, (traceGroup_e)tg, true);
	}
}

void
TraceManager::disableAllTraceObj(const char* aTraceObjName)
{
	for (size_t tg = 0; tg < END_GROUP; ++tg)
	{
		setTraceObjState(aTraceObjName, (traceGroup_e)tg, false);
	}
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
bool
TraceManager::isTraceObjEnabled(const char* aTraceObjName, traceGroup_e tg)
{
  if(tg < 0 || tg >= END_GROUP)
  {
	cerr << "Error, invalid value of traceGroup = " << tg << endl;
	return false;
  }
  TraceObj* pTraceObj = 0;
  if(findTraceObj(aTraceObjName, pTraceObj))
  {
	  return pTraceObj->isEnabled(tg);
  }
  return false;
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
const char* TraceManager::doStr(const char *format, ...)
{
  int     len;
  va_list args;

  va_start (args, format);
  len = vsnprintf(_traceBuffer, _traceBufferSize, format, args);
  va_end (args);

  if ((len < 0) || (len >= _traceBufferSize))
  {
    sprintf(_traceBuffer,
            "ERROR in TraceManager::doStr(const char *format, ...). "
            "Buffer of size=%d exceeded.",
            _traceBufferSize);

  }
  return _traceBuffer;
} // TraceManager::doStr

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
void TraceManager::enableTrace(traceGroup_e tg)
{
  _traceState[tg] = true;
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
void TraceManager::enableAllTrace()
{
  for (int i = 0; i< END_GROUP; ++i)
  {
    _traceState[i] = true;
  }
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
void TraceManager::disableTrace(traceGroup_e tg)
{
  _traceState[tg] = false;
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
void TraceManager::disableAllTrace()
{
  for (int i = 0; i< END_GROUP; ++i)
  {
    _traceState[i] = false;
  }
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
bool TraceManager::isTraceEnabled(traceGroup_e tg)
{
  if(tg < 0 || tg >= END_GROUP)
  {
	cerr << "Error, invalid value of traceGroup = " << tg << endl;
	return false;
  }
  return _traceState[tg];
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
void TraceManager::setTraceToFile(std::string aFileName)
{
  lock();
  _traceFile = aFileName;

  if (_OFS)
  {
    // previous stream is opened - closing
    _OFS->close();
    delete _OFS;
    _OFS = 0;
  }
  _OFS = new ofstream;
  _OFS->open(aFileName.data(), ios::out | ios::app);
  if (!_OFS->is_open())
  {
    cerr << "Trace::setTraceToFile("<<aFileName.data()
         << ") File not opened, redirecting to stderr.";
    _OFS->close();
    delete _OFS;
    _OFS = 0;
    _traceDirection = DIRECT_TO_STDERR;
  }
  else
  {
    _traceDirection = DIRECT_TO_FILE;
  }
  unlock();
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
void TraceManager::setTraceToStderr()
{
  lock();
  if (_OFS && _OFS->is_open())
  {
    _OFS->close();
    delete _OFS;
    _OFS = 0;
  }
  _traceDirection = DIRECT_TO_STDERR;
  unlock();
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
void TraceManager::setTraceToStdout()
{
  lock();
  if (_OFS && _OFS->is_open())
  {
    _OFS->close();
    delete _OFS;
    _OFS = 0;
  }
  _traceDirection = DIRECT_TO_STDOUT;
  unlock();
}

std::ostream&
TraceManager::printTraceHeader(
		const char*  aFilename,
        int          aLine,
        const char* objName,
        traceGroup_e aTraceGroup)
{
	const char* tgName = traceGroupName[aTraceGroup];
	return getStream() << timeStamp() << "[" << objName << "][" << tgName << "]["
			           << fileBaseName(aFilename) << ":" << aLine << "] ";
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
const char* TraceManager::timeStamp()
{
  time_t timeCnt = time(NULL);
  struct tm* sTime = localtime(&timeCnt);
  struct timeval now;
  gettimeofday(&now, NULL);

  sprintf(_traceTimeStamp, TIMESTAMP_FORMAT, TIMESTAMP_VALUES(sTime,now));

  return _traceTimeStamp;
}

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
const char* TraceManager::processName()
{
  return _processName.data();
} // Trace::processName

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
const char* TraceManager::fileBaseName(const char* filename)
{
  unsigned int idx = strlen(filename);

  while (idx && (*(filename + idx -1) != '/'))
    --idx;

  return (filename + idx);
} // Trace::fileBaseName

/*******************************************************************************
 *
 *
 *
 ******************************************************************************/
std::string TraceManager::hexDump(const void* aDataP, int aDataSize)
{
  // hexdump needs 77 bytes per line (16 bytes)
  // 1(tab) + 8(32-bit offset) + 2(separator) + 3*16(byte in hex and sep) + 1(separator) + 16(ascii) + 1(eol)
  // so 2464 bytes suffice for 512 byte hexdump.
  if (aDataSize > 512)
  {
    return string("\thexdump is limited to 512 bytes.\n");
  }

  int i;
  int  dumpOff = 0;
  char dump[2464];
  char ascii[17];
  const unsigned char *pc = reinterpret_cast<const unsigned char*>(aDataP);

  for (i=0; i<aDataSize; ++i)
  {
    if ((i % 16) == 0)
    {
      if (i != 0)
        dumpOff += sprintf(dump + dumpOff, "  %s\n", ascii);
      dumpOff += sprintf(dump + dumpOff, "\t%08X ", i);
    }

    dumpOff += sprintf(dump + dumpOff, " %02X", pc[i]);

    if ((pc[i] < 0x20) || (pc[i] > 0x7e))
      ascii[i % 16] = '.';
    else
      ascii[i % 16] = pc[i];
    ascii[(i % 16) + 1] = '\0';
  }

  // Pad out last line if not exactly 16 characters.
  while ((i % 16) != 0) {
    dumpOff += sprintf(dump + dumpOff, "   ");
    ++i;
  }
  // And print the final ASCII bit.
  dumpOff += sprintf(dump + dumpOff, "  %s\n", ascii);

  return string(dump);
} // Trace::hexDump

bool
TraceManager::findTraceObj(const char* aTraceObjName, TraceObj*& to)
{
	if(aTraceObjName == NULL)
	{
		return false;
	}
	TraceObjMap::iterator it;
	string str(aTraceObjName);
	it = _traceObjMap.find(str);
	if(it != _traceObjMap.end())
	{
		to = it->second;
		return true;
	}
	return false;
}

std::ostream&
TraceManager::getStream()
{
	switch(_traceDirection)
	{
		case DIRECT_TO_FILE:
		{
		  if (_OFS && _OFS->is_open())
		  {
			return *_OFS;
		  }
		  break;
		}
		case DIRECT_TO_STDERR:
		{
		  return cerr;
		}
		case DIRECT_TO_STDOUT:
		{
		  return cout;
		}
	}
	cerr << "Error: Unable to determine correct stream, returning cout" << endl;
	return cout;
}

} // namespace common
} // namespace sctp_sim


